#pragma once
#include <iostream>
using namespace std;
#include "mythread.hpp"
#include <queue>
#include <vector>
#include <string>

template <class T>
class threadPool
{
    static void *handle(void *args)
    {
        // 需要从args中获取线程池的地址,才能访问线程池的成员
        threadPool<T> *_this = static_cast<threadPool<T> *>(args);
        while (true)
        {
            // 消费者先从队列中读取任务
            T newtask = _this->pop();
            // 格式化线程名
            char buffer[64] = {0};
            snprintf(buffer, sizeof(buffer), "0x%x", pthread_self());
            // 执行任务
            cout << "线程" << buffer << "消费了一个任务" << newtask() << endl;
        }

        return nullptr;
    }

public:
    // 构造函数
    threadPool(int threadNum = 5)
        : _threadNum(threadNum)
    {
        pthread_cond_init(&_cuscond, nullptr);  // 初始化消费者使用的条件变量
        pthread_mutex_init(&_publock, nullptr); // 初始化公共线程锁

        // 创建_threadNum个消费者线程
        for (int i = 0; i < _threadNum; i++)
        {
            // 创建线程时，将线程池的地址传递给任务函数，方便任务函数中能够访问线程池的成员
            _pool.push_back(new thread(handle, (void *)this));
        }
    }

    // 析构函数
    ~threadPool()
    {
        pthread_cond_destroy(&_cuscond);  // 释放消费者使用的条件变量
        pthread_mutex_destroy(&_publock); // 释放公共线程锁

        // 将创建的线程全部释放掉
        for (int i = 0; i < _threadNum; i++)
        {
            delete _pool[i];
        }
    }

    // 生产者插入数据(没有数量上限)
    void push(const T &val)
    {
        pthread_mutex_lock(&_publock);   // 上锁，只允许一个线程访问队列
        _que.push(val);                  // 插入任务
                                         // 此时队列中至少有一个任务
        pthread_cond_signal(&_cuscond);  // 唤醒_cuscond条件变量下阻塞的一个消费者线程
        pthread_mutex_unlock(&_publock); // 数据插入完成后，解锁
    }

    // 消费者读取数据(需要先判空)
    T pop()
    {
        pthread_mutex_lock(&_publock); // 上锁，只允许一个线程访问队列
        while (is_empty())
        {
            // 队列为空，将消费者线程在_cuscond条件变量下挂起
            pthread_cond_wait(&_cuscond, &_publock);
            // 当生产者线程传入数据后，消费者线程才会被唤醒
            // 可能有多个消费者线程同时被唤醒，且都处于临界区内，pthread_cond_wait在线程醒来后后自动申请互斥锁
            // 当再次申请到互斥锁后，有可能其他消费者率先醒来取走数据，再次使得队列为空，因此需要重新检查队列状态
        }

        // 队列中一定存在数据，取出队头数据
        T val = _que.front();
        _que.pop();

        // 数据读取完成后，解锁,然后返回读取到的数据
        pthread_mutex_unlock(&_publock);
        return val;
    }

private:
    // 判断队列释放为空
    bool is_empty()
    {
        return _que.empty();
    }

private:
    vector<thread *> _pool;   // 线程列表
    int _threadNum;           // 线程个数
    queue<T> _que;            // 不限制队列的大小，生产者插入数据没有数量上限
    pthread_cond_t _cuscond;  // 供消费者使用的条件变量。当队列为空时，将消费者在该条件变量下挂起
    pthread_mutex_t _publock; // 所有线程共用的锁，保护队列在任何时刻只能被一个线程访问
};